﻿// Inner Kit 通用工具库
// SpritePack（精灵打包类）
//
// Copyright (c) 2024 by 尔西大帝. All rights reserved.
//
// 这个类用于将一系列的小图片打包成一个大图片包。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-12-22

#include "sprite_pack.h"
#include <DirectXMath.h>
#include <array>
#include <tinyxml2.h>
#include <klog.h>
#include <algorithm>

using namespace DirectX;

// 一些快捷操作，避免引用太多的头文件导致编译时间过长，就写这儿吧。

//#define max(a, b) (((a) > (b)) ? (a) : (b))
//#define min(a, b) (((a) < (b)) ? (a) : (b))

static constexpr int GetPage(int count, int page_size) {
  return count / page_size + (count % page_size == 0 ? 0 : 1);
}

////////////////////////////////////////////////////////////////////////////////
// 快捷操作
////////////////////////////////////////////////////////////////////////////////
namespace ikit {

static XMFLOAT2 RotateXMFLOAT2(XMFLOAT2 pos, float angle) {
  float sin_a = sinf(angle);
  float cos_a = cosf(angle);
  float new_x = pos.x * cos_a - pos.y * sin_a;
  float new_y = pos.x * sin_a + pos.y * cos_a;
  return XMFLOAT2(new_x, new_y);
}

/// <summary>
/// 对于一个BOX进行旋转
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="angle">这是弧度，而不是度数</param>
/// <returns></returns>
static RectF GetRotationBox(int width, int height, float angle) {
  float width_f = static_cast<float>(width);
  float height_f = static_cast<float>(height);
  // 初始化4个角，要转化为迪卡尔坐标系（也就是和屏幕空间是反的）
  XMFLOAT2 vectors[4]{
      XMFLOAT2(-width_f * 0.5f, height_f * 0.5f),  // 左上
      XMFLOAT2(width_f * 0.5f, height_f * 0.5f),   // 右上
      XMFLOAT2(-width_f * 0.5f, -height_f * 0.5f), // 左下
      XMFLOAT2(width_f * 0.5f, -height_f * 0.5f)   // 右下
  };
  // 记录边框
  RectF box = {0, 0, 0, 0};

  for (int i = 0; i < 4; i++) {
    // 对角进行旋转之后，记录下位置
    XMFLOAT2 rot = RotateXMFLOAT2(vectors[i], angle);
    box.left = std::min(box.left, rot.x);
    box.top = std::min(box.top, rot.y);
    box.right = std::max(box.right, rot.x);
    box.bottom = std::max(box.bottom, rot.y);
  }

  return box;
}

constexpr std::array<int, 5> TEXTURE_SIZE_ARRAY = {512, 1024, 2048, 4096, 8192};

SpritePack::SpritePack(const XmlNode& node, int index) {
  // 是否使用COLORKEY
  name = node("Name");
  alphaToColorKey = node("AlphaToColorKey", false);

  node.Foreach("Sprite", [this](XmlNode& sprite_node, int index) {
    LPSpriteElement group = new SpriteElement(this, index, sprite_node);
    items.push_back(group);
  });

  Load();
  Build(1.0f);

  // 将项目限制为2的次幂，因为贴图只能是这样
  int max_value = std::max(width, height);
  int fixed_size = TEXTURE_SIZE_ARRAY[0];
  for (size_t i = 0; i < TEXTURE_SIZE_ARRAY.size() - 1; i++) {
    if (max_value > TEXTURE_SIZE_ARRAY[i]) {
      fixed_size = TEXTURE_SIZE_ARRAY[i + 1];
    }
  }

  // 重构项目
  img.width = fixed_size;
  img.height = fixed_size;
  texture_size = static_cast<float>(fixed_size);

  img.channels = 4;
  auto size = img.width * img.height * sizeof(uint32_t);
  img.data = (unsigned char*)malloc(size);
  memset(img.data, 0, size);
  // 注意因为是按32位写入的，所以pitch是32位，不需要乘sizeof(uint32_t)
  Fill((uint32_t*)img.data, img.width);
}

SpritePack::~SpritePack() {
  for (auto i : items) {
    delete i;
  }
  free(img.data);
}

void SpritePack::Build(float rate) {
  /*
   * Build的思路：类似一种深度缓冲机制，把画板设置为32x32每格。然后询问每一个Group占有多少格，占到了格子，就使用深度缓冲标记为1。
   * 这样依次询问每一个Group，使其占有格子。
   */
  SpriteGrid grid(this, rate);

  // 对Group进行轮询
  for (auto g : items) {
    // 填充一个项目，如果填充失败，则将范围增大一倍
    if (!grid.Stack(g)) {
      KLOG("Build %s failed, grid is %dx%d.", name, grid.grid_size,
          grid.grid_size);

      // 最大范围是10倍
      if (rate < 10.0f) {
        Build(rate + 1.0f);
        return;
      } else {
        KLOG("Build %s failed, reach max rate.", name);
        return;
      }
    }
  }

  // 得到裁剪后大小
  width = (grid.x_far + 1) * SPRITE_PACK_BLOCK;
  height = (grid.y_far + 1) * SPRITE_PACK_BLOCK;
}

void SpritePack::Fill(std::uint32_t* buffer, int lpitch) {
  for (auto g : items) {
    g->Fill(buffer, lpitch);
  }
}

LPSpriteImage SpritePack::Find(const char* name, int index) {
  for (auto g : items) {
    if (strcasecmp(g->name.c_str(), name) == 0) {
      return g->Find(index);
    }
  }
  return NULL;
}

LPSpriteElement SpritePack::FindElement(const char* name) {
  for (auto g : items) {
    if (strcasecmp(g->name.c_str(), name) == 0) {
      return g;
    }
  }
  return NULL;
}

LPSpriteElement SpritePack::FindElement(ikit::sstr name) {
  for (auto g : items) {
    if (g->name == name) {
      return g;
    }
  }
  return NULL;
}

bool SpritePack::IsLoaded(void) const { return is_loaded; }

void SpritePack::Load(void) {
  for (auto g : items) {
    g->Load();
  }
}

void SpritePack::Pack(void) {}

////////////////////////////////////////////////////////////////////////////////
// SpriteGroup
////////////////////////////////////////////////////////////////////////////////
SpriteElement::SpriteElement(LPSpritePack parent, int id, const XmlNode& node)
    : parent(parent), id(id) {

  count = node.GetCount("Image");
  column = node("Column");
  name = node("Name");
  items = (LPSpriteImage*)malloc(sizeof(LPSpriteImage) * count);

  node.Foreach("Image", [this](XmlNode& image_node, int img_index) {
    items[img_index] = new SpriteImage(this, img_index, image_node);
  });

  // 计算row
  if (column == 0) {
    column = count;
    row = 1;
  } else {
    row = GetPage(count, column);
  }
}

SpriteElement::~SpriteElement() {
  for (int i = 0; i < count; i++) {
    LPSpriteImage item = items[i];
    if (item) {
      delete item;
    }
  }
  free(items);
}

void SpriteElement::Add(LPSpriteImage e, int index) { items[index] = e; }

void SpriteElement::Load(void) {
  grid_width = 0;
  grid_height = 0;

  // 载入所有的项目
  for (int i = 0; i < count; i++) {
    items[i]->Load();

    // 处理宽高
    if (items[i]->IsValid()) {
      grid_width = std::max(grid_width, items[i]->width);
      grid_height = std::max(grid_height, items[i]->height);
    }
  }

  // 根据Row和Column计算整体的大小（以最大的项目为标准）
  width = grid_width * column;
  height = grid_height * row;
}

void SpriteElement::Print(void) {
  int gx = GetPage(width, SPRITE_PACK_BLOCK);
  int gy = GetPage(height, SPRITE_PACK_BLOCK);
  for (int y = 0; y < gy; y++) {
    for (int x = 0; x < gx; x++) {
      KPRINT("%d", id);
    }
    KPRINT("\n");
  }
}

void SpriteElement::Fill(std::uint32_t* buffer, int lpitch) {
  for (int iy = 0; iy < row; iy++) {
    for (int ix = 0; ix < column; ix++) {
      int pointer = iy * column + ix;
      if (pointer >= count) { // 已经越界
        return;
      }

      // 计算绘制的开始点
      int xstart = ix * grid_width + x;
      int ystart = iy * grid_height + y;

      // 得到项目
      LPSpriteImage e = items[pointer];
      e->Fill(buffer, lpitch, xstart, ystart);
    }
  }
}

LPSpriteImage SpriteElement::First(void) const { return items[0]; }

LPSpriteImage SpriteElement::Find(int index) const {
  if (index < 0 || index >= count) {
    return NULL;
  }
  return items[index];
}

////////////////////////////////////////////////////////////////////////////////
// SpriteElement
////////////////////////////////////////////////////////////////////////////////

SpriteImage::SpriteImage(LPSpriteElement parent, int index, const XmlNode& node)
    : parent(parent), index(index), width(0), height(0), png() {
  // 项目的值便是路径，不是属性
  filename = node.Value();
  // 这里的度数，要进行一个反转，常识的度数是顺时针转动，而数学运算是向逆时针转动。因为图片的Y轴和笛卡尔坐标是相反的。
  rotation = node("Rotation");
  rotation = -rotation;
  flipX = node("FlipX");
  flipY = node("FlipY");
  rect = {0, 0, 0, 0};
}

SpriteImage::~SpriteImage() { png.Release(); }

void SpriteImage::Load(void) {
  png = ImageData(to_wide(filename), true);
  png.ConvertToChannels4(false);

  width = png.width;
  height = png.height;

  // 计算真实的大小（将4个角旋转，然后获得新的坐标）
  if (rotation != 0) {
    RectF box = GetRotationBox(width, height, rotation * DEG2RAD);
    // 新的大小
    width = static_cast<int>(ceil(box.right - box.left));
    height = static_cast<int>(ceil(box.bottom - box.top));
  }
}

bool SpriteImage::IsValid(void) const { return png.IsValid(); }

void SpriteImage::Fill(std::uint32_t* buffer, int lpitch, int x, int y) {
  int pointer = 0;

  // 根据是否旋转，决定不同的处理
  if (rotation != 0) {
    int pic_width = png.width;
    int pic_height = png.height;
    RectF box = GetRotationBox(pic_width, pic_height, rotation * DEG2RAD);
    float sin_a = sinf(rotation * DEG2RAD);
    float cos_a = cosf(rotation * DEG2RAD);
    XMFLOAT2 offset = XMFLOAT2(static_cast<float>(pic_width) * 0.5f,
        static_cast<float>(pic_height) * 0.5f);

    // 得到{0.0}旋转的位置
    XMFLOAT2 origin = XMFLOAT2(
        static_cast<float>(width) * 0.5f, static_cast<float>(height) * 0.5f);

    // 对新图像进行填充
    for (int iy = 0; iy < height; iy++) {
      for (int ix = 0; ix < width; ix++) {
        // 寻找在图片中的位置
        pointer = (y + iy) * lpitch + (x + ix);
        // 配置颜色
        buffer[pointer] = SPRITE_PACK_COLOR_KEY;
      }
    }

    for (int iy = 0; iy < pic_height; iy++) {
      for (int ix = 0; ix < pic_width; ix++) {
        // 将坐标点转为笛卡尔坐标
        XMFLOAT2 pos =
            XMFLOAT2(ix - pic_width * 0.5f, (pic_height * 0.5f) - iy);

        // 进行旋转
        int px = static_cast<int>(pos.x * cos_a - pos.y * sin_a);
        int py = static_cast<int>(pos.x * sin_a + pos.y * cos_a);

        // 补回位移
        px = px + static_cast<int>(origin.x);
        py = (height / 2) - py; // 转回屏幕坐标

        // 寻找在图片中的位置
        pointer = (y + py) * lpitch + (x + px);
        // 配置颜色
        std::uint32_t color = PickPixel(ix, iy);
        buffer[pointer] = color;

        // 进行插值，比较粗暴的算法，得到周围格子，如果是COLORKEY，则进行填充
        // 我看了看位图的插值算法，感觉以后再优化。可以用双线性插值来计算
        for (int jx = -1; jx <= 1; jx++) {
          for (int jy = -1; jy <= 1; jy++) {
            int newx = (x + px + jx);
            int newy = (y + py + jy);
            if (newx >= x && newx < (x + width) && newy >= y &&
                newy < (y + height)) {
              pointer = newy * lpitch + newx;
              if (buffer[pointer] == SPRITE_PACK_COLOR_KEY) {
                buffer[pointer] = color;
              }
            }
          }
        }
      }
    }
  } else {
    pointer = y * lpitch + x;
    for (int iy = 0; iy < height; iy++) {
      for (int ix = 0; ix < width; ix++) {
        buffer[pointer] = PickPixel(ix, iy);
        pointer++;
      }
      pointer = (y + iy) * lpitch + x;
    }
  }

  // 设置自已的空间
  rect.left = x;
  rect.top = y;
  rect.right = x + width - 1;
  rect.bottom = y + height - 1;
  // 转化为float以供贴图使用
  rectf.left = static_cast<float>(rect.left);
  rectf.top = static_cast<float>(rect.top);
  rectf.right = static_cast<float>(rect.right);
  rectf.bottom = static_cast<float>(rect.bottom);
}

std::uint32_t SpriteImage::PickPixel(int x, int y) const {
  const std::uint32_t* buffer = (std::uint32_t*)png.data;

  int raw_width = png.width;
  int raw_height = png.height;

  // 检查是否反转
  if (flipX) {
    x = (raw_width - 1) - x;
  }
  if (flipY) {
    y = (raw_height - 1) - y;
  }

  return buffer[y * raw_width + x];
}

const unsigned char* SpriteImage::GetBuffer() const { return png.data; }

int SpriteImage::GetBitmapWidth(void) const { return png.width; }

int SpriteImage::GetBitmapHeight(void) const { return png.height; }

bool SpriteGrid::IsStackable(int x, int y, int gx, int gy) {

  // 移动到开始位置
  char* grid_pointer = grid + (y * grid_size + x);

  // 进行循环判断是否有项目
  for (int iy = 0; iy < gy; iy++) {
    for (int ix = 0; ix < gx; ix++) {
      if (grid_pointer[ix] > 0) {
        return false;
      }
    }
    // PS（这里我曾经用grid_size -
    // 1来做一个加法使指针步进，但不知道为什么会寻址出错！）
    grid_pointer = grid + ((y + iy) * grid_size + x);
  }

  // 所有的格子都是0，成功
  return true;
}

SpriteGrid::SpriteGrid(LPSpritePack pack, float rate) {
  // 统计所有项目的面积，得到一个估算的面积
  int total = 0;
  for (auto g : pack->items) {
    total += g->width * g->height;
  }

  // 得到一个面积（当这个面积无法处理的时候，使用递归增大面积）乘2是为了让格子拥有容错。
  int total_size =
      static_cast<int>(sqrtf(static_cast<float>(total)) * rate * 1.5f);
  // 这个面积化为32x32的格子。这里不用执着求余，直接加1就行。
  grid_size = total_size / SPRITE_PACK_BLOCK + 1;

  // 建立格子
  grid = (char*)malloc(grid_size * grid_size);
  memset(grid, 0, grid_size * grid_size);

  // 初始化最远距离
  x_far = 0;
  y_far = 0;
}

SpriteGrid::~SpriteGrid() { free(grid); }

bool SpriteGrid::Stack(LPSpriteElement g) {
  // 计算出元素所需要的格子
  int gx = GetPage(g->width, SPRITE_PACK_BLOCK);
  int gy = GetPage(g->height, SPRITE_PACK_BLOCK);

  // 计算最远的距离（必须要留出指定的宽度）
  int xlimit = grid_size - (gx - 1);
  int ylimit = grid_size - (gy - 1);

  // 根据哪个方向更短，决定求的范围
  // 如果X方向更加空，则求X
  if (x_far < y_far) {
    for (int y = 0; y < ylimit; y++) {
      for (int x = 0; x < xlimit; x++) {
        if (IsStackable(x, y, gx, gy)) {
          Fill(g, x, y);
          return true;
        }
      }
    }
  } else {
    for (int x = 0; x < xlimit; x++) {
      for (int y = 0; y < ylimit; y++) {
        if (IsStackable(x, y, gx, gy)) {
          Fill(g, x, y);
          return true;
        }
      }
    }
  }

  return false;
}

void SpriteGrid::Print() {
  // 全部填充成功，准备制作面
  for (int y = 0; y < grid_size; y++) {
    for (int x = 0; x < grid_size; x++) {
      if (x > x_far || y > y_far) {
        KPRINT("-");
      } else {
        KPRINT("%d", grid[x + grid_size * y]);
      }
    }
    KPRINT("\n");
  }
}

void SpriteGrid::Fill(LPSpriteElement g, int x, int y) {
  // 计算出元素所需要的格子
  int gx = GetPage(g->width, SPRITE_PACK_BLOCK);
  int gy = GetPage(g->height, SPRITE_PACK_BLOCK);

  // 依次填充（这里不需要节约计算，因为填充的次数是非常少的，判断才多）
  for (int ix = 0; ix < gx; ix++) {
    for (int iy = 0; iy < gy; iy++) {
      int pointer = ix + x + (iy + y) * grid_size;
      grid[pointer] = (g->id + 1);
    }
  }

  // 在Group中指定位置
  g->x = x * SPRITE_PACK_BLOCK;
  g->y = y * SPRITE_PACK_BLOCK;

  // 配置最远距离
  x_far = std::max(x_far, x + gx - 1);
  y_far = std::max(y_far, y + gy - 1);
}

} // namespace ikit